#pragma once

#include "../DefinesCore.hpp"
#include <string>
#include <map>

CYBERTRON_BEGIN

template <typename EnumType, typename EnumTypeTraits>
class EnumObject
{
public:
	EnumObject()
	{
		mValue = (EnumType)0;
	}

	EnumObject(EnumType val)
	{
		mValue = val;
	}

	EnumObject(const EnumObject<EnumType, EnumTypeTraits>& rhs)
	{
		mValue = rhs.mValue;
	}

	EnumObject& operator = (const EnumObject<EnumType, EnumTypeTraits>& rhs)
	{
		mValue = rhs.mValue;
		return *this;
	}

	EnumObject& operator = (EnumType rhs)
	{
		mValue = rhs;
		return *this;
	}

	~EnumObject()
	{
	}

	operator EnumType() const
	{
		return (EnumType)mValue;
	}

	EnumType getValue() const
	{
		return (EnumType)mValue;
	}

	std::string toString() const
	{
		return EnumTypeTraits::toString(mValue);
	}

	void fromString(const std::string& str)
	{
		mValue = (EnumType)(EnumTypeTraits::fromString(str));
	}

	bool operator == (const EnumObject<EnumType, EnumTypeTraits>& rhs) const
	{
		return mValue == rhs.mValue;
	}

	bool operator != (const EnumObject<EnumType, EnumTypeTraits>& rhs) const
	{
		return mValue != rhs.mValue;
	}

	bool operator == (EnumType rhs) const
	{
		return mValue != rhs;
	}

	bool operator != (EnumType rhs) const
	{
		return mValue != rhs;
	}

private:
	EnumType mValue;
};

// Declare Enum Example: 
// 
// enum EType {
//   EType_int, 
//   EType_float, 
// };
// 
// EnumObjectDeclare(EType);
// 

// Usage Example: 
// 
// ETypeObject type = EType_float;
// if (type == EType_float) { do something. }
// std::string name = type.toString();
// type.fromString("int");
// EType rawType = type.getValue();
// 
#define EnumObjectDeclare(enumName) \
	enum enumName;\
	class TypeTraits_##enumName { \
		public:\
		static std::map<std::string, int> smStringToInt;\
		static std::map<int, std::string> smIntToString;\
		static bool smInitialized;\
		static void setup();\
		static inline std::string toString(int val) {\
			setup();\
			auto it = smIntToString.find(val);\
			if (it == smIntToString.end()) return std::string();\
			return (*it).second;\
		}\
		static inline int fromString(const std::string& val) {\
			setup();\
			auto it = smStringToInt.find(val);\
			if (it == smStringToInt.end()) return -1;\
			return (*it).second;\
		}\
	};\
	typedef cybertron::EnumObject<enumName, TypeTraits_##enumName> enumName##Object;

#define EnumObjectDefineBegin(enumName) \
	std::map<std::string, int> TypeTraits_##enumName::smStringToInt;\
	std::map<int, std::string> TypeTraits_##enumName::smIntToString;\
	bool TypeTraits_##enumName::smInitialized = false;\
	void TypeTraits_##enumName::setup() {\
		if (smInitialized) return;\
		smInitialized = true;

#define EnumObjectEntry(enumValue) \
	smStringToInt[#enumValue] = (int)enumValue;\
	if (smIntToString.find((int)enumValue) == smIntToString.end()) {\
		smIntToString[(int)enumValue] = #enumValue;\
	}

#define EnumObjectNamedEntry(name, enumValue) \
	smStringToInt[name] = (int)enumValue;\
	if (smIntToString.find((int)enumValue) == smIntToString.end()) {\
		smIntToString[(int)enumValue] = name;\
	}

#define EnumObjectDefineEnd() }

CYBERTRON_END
